﻿using System;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using Medora.Model;
using Medora.Model.Basic;

namespace Medora.Services.Utils
{
    public class FilterConstructor<T, B> : IFilterConstructor<T, B> where T : IFilter where B: Entity
    {
        public Expression<Func<B, bool>> GetFilter(T filter, Expression<Func<B, bool>> startExpresion)
        {
            var filterType=filter.GetType();
            
            var properties = filterType.GetProperties();
            Expression<Func<B, bool>>  finalExpression = startExpresion;
            foreach (var propertyInfo in properties)
            {
                var t=propertyInfo.PropertyType.ToString();
                var value = propertyInfo.GetValue(filter);
                if (value != null && value.ToString()!=Guid.Empty.ToString())
                {
                    Expression<Func<B, bool>> result=null;
                    ParameterExpression param = Expression.Parameter(typeof(B), propertyInfo.Name);
                    switch (t)
                    {
                        case "System.String":
                            var bodyMc = GetExpressionContains(propertyInfo.Name, value.ToString(), param);
                            result = Expression.Lambda<Func<B, bool>>(bodyMc, new ParameterExpression[] { param });
                            break;
                        case "System.DateTime":
                        case "System.Nullable`1[System.DateTime]":
                            string fieldName;
                            if (propertyInfo.Name.Contains("DFrom"))
                            {
                                fieldName = propertyInfo.Name.Replace("DFrom", "");
                                var bodyDate=Expression.GreaterThanOrEqual(Expression.PropertyOrField(param, fieldName),
                                                           Expression.Constant(value, typeof (DateTime?)));
                                result = Expression.Lambda<Func<B, bool>>(bodyDate, new ParameterExpression[] { param });
                            }
                            if (propertyInfo.Name.Contains("DTo"))
                            {
                                fieldName = propertyInfo.Name.Replace("DTo", "");
                                var bodyDate = Expression.LessThanOrEqual(Expression.PropertyOrField(param, fieldName),
                                                           Expression.Constant(value, typeof(DateTime?)));
                                result = Expression.Lambda<Func<B, bool>>(bodyDate, new ParameterExpression[] { param });
                            }
                            break;
                        case "System.Nullable`1[System.Guid]":
                            Guid? g = ((Guid?)value);
                            var bodyGuid = Expression.Equal(Expression.PropertyOrField(param, propertyInfo.Name), Expression.Constant(g,typeof(Guid?)));
                            result = Expression.Lambda<Func<B, bool>>(bodyGuid, new ParameterExpression[] { param });
                            break;
                        case "System.Guid":
                            Guid gval = ((Guid)value);
                            var bG = Expression.Equal(Expression.PropertyOrField(param, propertyInfo.Name), Expression.Constant(gval, typeof(Guid)));
                             result = Expression.Lambda<Func<B, bool>>(bG, new ParameterExpression[] { param });
                            break;
                        default:
                            var bodyEq = GetExpressionEqual(propertyInfo.Name, value, param);
                            result = Expression.Lambda<Func<B, bool>>(bodyEq, new ParameterExpression[] { param });
                            break;
                    }
                    if (result != null) finalExpression = And(finalExpression, result);
                }
            }
            return finalExpression;

        }

        private BinaryExpression GetExpressionEqual(string filterField, object filterValue, ParameterExpression param)
        {
            var body = Expression.Equal(Expression.PropertyOrField(param, filterField), Expression.Constant(filterValue.ToString()));
            return body;
        }

        private MethodCallExpression GetExpressionContains(string filterField, string filterValue, ParameterExpression param)
        {
            MethodInfo method = typeof(string).GetMethod("Contains", new[] { typeof(string) });
            var value = Expression.Constant(filterValue);
            var body = Expression.Call(Expression.PropertyOrField(param, filterField), method, value);
            return body;
        }

        /*private Expression<B> Compose<B>(Expression<B> first, Expression<B> second, Func<Expression, Expression, Expression> merge)
        {
            // build parameter map (from parameters of second to parameters of first)
            var map = first.Parameters.Select((f, i) => new { f, s = second.Parameters[i] }).ToDictionary(p => p.s, p => p.f);

            // replace parameters in the second lambda expression with parameters from the first
            var secondBody = ParameterRebinder.ReplaceParameters(map, second.Body);

            // apply composition of lambda expression bodies to parameters from the first expression 
            return Expression.Lambda<B>(merge(first.Body, secondBody), first.Parameters);
        }*/

        public Expression<Func<B, bool>> And<B>(Expression<Func<B, bool>> first, Expression<Func<B, bool>> second)
        {
            return first.Compose(second, Expression.And);
        }

        public Expression<Func<B, bool>> Or<B>(Expression<Func<B, bool>> first, Expression<Func<B, bool>> second)
        {
            return first.Compose(second, Expression.Or);
        }
    }
}

